package xyz.luan.audioplayers

import android.content.Context
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.media.MediaPlayer.*
import android.os.Build
import android.os.PowerManager
import java.io.IOException

class WrappedMediaPlayer internal constructor(
    private val ref: AudioplayersPlugin,
    override val playerId: String
) : Player(), OnPreparedListener, OnCompletionListener, OnSeekCompleteListener {
    private var url: String? = null
    private var volume: Double = 1.0
    private var rate: Float = 1.0f
    private var respectSilence: Boolean = false
    private var stayAwake: Boolean = false
    private var releaseMode: ReleaseMode? = ReleaseMode.RELEASE
    private var playingRoute: String? = "speakers"
    private var released: Boolean = true
    private var prepared: Boolean = false
    private var playing: Boolean = false
    private var shouldSeekTo: Int = -1
    private var player: MediaPlayer? = null

    /**
     * Setter methods
     */
    public override fun setUrl(url: String?, isLocal: Boolean, context: Context?) {
        if (!objectEquals(this.url, url)) {
            this.url = url
            if (released) {
                player = createPlayer(context)
                released = false
            } else if (prepared) {
                player!!.reset()
                prepared = false
            }
            setSource(url)
            player!!.setVolume(volume.toFloat(), volume.toFloat())
            player!!.setLooping(releaseMode == ReleaseMode.LOOP)
            player!!.prepareAsync()
        }
    }

    public override fun setVolume(volume: Double) {
        if (this.volume != volume) {
            this.volume = volume
            if (!released) {
                player!!.setVolume(volume.toFloat(), volume.toFloat())
            }
        }
    }

    public override fun setPlayingRoute(playingRoute: String?, context: Context?) {
        if (!objectEquals(this.playingRoute, playingRoute)) {
            val wasPlaying: Boolean = playing
            if (wasPlaying) {
                pause()
            }
            this.playingRoute = playingRoute
            var position: Int = 0
            if (player != null) {
                position = player!!.getCurrentPosition()
            }
            released = false
            player = createPlayer(context)
            setSource(url)
            try {
                player!!.prepare()
            } catch (ex: IOException) {
                throw RuntimeException("Unable to access resource", ex)
            }
            seek(position)
            if (wasPlaying) {
                playing = true
                player!!.start()
            }
        }
    }

    public override fun setRate(rate: Double): Int {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            throw UnsupportedOperationException("The method 'setRate' is available only on Android SDK version " + Build.VERSION_CODES.M + " or higher!")
        }
        if (player != null) {
            this.rate = rate.toFloat()
            player!!.setPlaybackParams(player!!.getPlaybackParams().setSpeed(this.rate))
            return 1
        }
        return 0
    }

    public override fun configAttributes(
        respectSilence: Boolean,
        stayAwake: Boolean,
        context: Context?
    ) {
        if (this.respectSilence != respectSilence) {
            this.respectSilence = respectSilence
            if (!released) {
                setAttributes(player, context)
            }
        }
        if (this.stayAwake != stayAwake) {
            this.stayAwake = stayAwake
            if (!released && this.stayAwake) {
                player!!.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK)
            }
        }
    }

    public override fun setReleaseMode(releaseMode: ReleaseMode?) {
        if (this.releaseMode != releaseMode) {
            this.releaseMode = releaseMode
            if (!released) {
                player!!.setLooping(releaseMode == ReleaseMode.LOOP)
            }
        }
    }

    /**
     * Getter methods
     */
    override val duration: Int
        get() {
            return if (player != null) player!!.getDuration() else 0
        }
    override val currentPosition: Int
        get() {
            return player!!.currentPosition
        }
    override val isActuallyPlaying: Boolean
        get() {
            return playing && prepared
        }

    /**
     * Playback handling methods
     */
    public override fun play(context: Context?) {
        if (!playing) {
            playing = true
            if (released) {
                released = false
                player = createPlayer(context)
                setSource(url)
                player!!.prepareAsync()
            } else if (prepared) {
                player!!.start()
                ref.handleIsPlaying(this)
            }
        }
    }

    public override fun stop() {
        if (released) {
            return
        }
        if (releaseMode != ReleaseMode.RELEASE) {
            if (playing) {
                playing = false
                player!!.pause()
                player!!.seekTo(0)
            }
        } else {
            release()
        }
    }

    public override fun release() {
        if (released) {
            return
        }
        if (playing) {
            player!!.stop()
        }
        player!!.reset()
        player!!.release()
        player = null
        prepared = false
        released = true
        playing = false
    }

    public override fun pause() {
        if (playing) {
            playing = false
            player!!.pause()
        }
    }

    // seek operations cannot be called until after
    // the player is ready.
    public override fun seek(position: Int) {
        if (prepared) player!!.seekTo(position) else shouldSeekTo = position
    }

    /**
     * MediaPlayer callbacks
     */
    public override fun onPrepared(mediaPlayer: MediaPlayer) {
        prepared = true
        ref.handleDuration(this)
        if (playing) {
            player!!.start()
            ref.handleIsPlaying(this)
        }
        if (shouldSeekTo >= 0) {
            player!!.seekTo(shouldSeekTo)
            shouldSeekTo = -1
        }
    }

    public override fun onCompletion(mediaPlayer: MediaPlayer) {
        if (releaseMode != ReleaseMode.LOOP) {
            stop()
        }
        ref.handleCompletion(this)
    }

    public override fun onSeekComplete(mediaPlayer: MediaPlayer) {
        ref.handleSeekComplete(this)
    }

    /**
     * Internal logic. Private methods
     */
    private fun createPlayer(context: Context?): MediaPlayer {
        val player: MediaPlayer = MediaPlayer()
        player.setOnPreparedListener(this)
        player.setOnCompletionListener(this)
        player.setOnSeekCompleteListener(this)
        setAttributes(player, context)
        player.setVolume(volume.toFloat(), volume.toFloat())
        player.setLooping(releaseMode == ReleaseMode.LOOP)
        return player
    }

    private fun setSource(url: String?) {
        try {
            player!!.setDataSource(url)
        } catch (ex: IOException) {
            throw RuntimeException("Unable to access resource", ex)
        }
    }

    private fun setAttributes(player: MediaPlayer?, context: Context?) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (objectEquals(playingRoute, "speakers")) {
                player!!.setAudioAttributes(
                    AudioAttributes.Builder()
                        .setUsage(if (respectSilence) AudioAttributes.USAGE_NOTIFICATION_RINGTONE else AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build()
                )
            } else {
                // Works with bluetooth headphones
                // automatically switch to earpiece when disconnect bluetooth headphones
                player!!.setAudioAttributes(
                    AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build()
                )
                if (context != null) {
                    val mAudioManager: AudioManager =
                        context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
                    mAudioManager.setSpeakerphoneOn(false)
                }
            }
        } else {
            // This method is deprecated but must be used on older devices
            if (objectEquals(playingRoute, "speakers")) {
                player!!.setAudioStreamType(if (respectSilence) AudioManager.STREAM_RING else AudioManager.STREAM_MUSIC)
            } else {
                player!!.setAudioStreamType(AudioManager.STREAM_VOICE_CALL)
            }
        }
    }
}